#ifndef __SHADER_PARAMETER_H__
#define __SHADER_PARAMETER_H__

#pragma once

#include "RenderingConfig.h"

namespace Nezha
{

	// REMARK !!
#define MAX_SHADER_PARAM_NAME_LEN 64

	enum ShaderParamCategory
	{
		SPC_BUILTIN,
		SPC_SAMPLER,
		SPC_APP,
	};

	enum ShaderParamDataType
	{
		SPDT_FIXED,
		SPDT_FIXED2,
		SPDT_FIXED3,
		SPDT_FIXED2X3,
		SPDT_FIXED3X3,
		SPDT_FIXED4X3,
		SPDT_FIXED4,
		SPDT_FIXED2X4,
		SPDT_FIXED3X4,
		SPDT_FIXED4X4,

		SPDT_HALF,
		SPDT_HALF2,
		SPDT_HALF3,
		SPDT_HALF2X3,
		SPDT_HALF3X3,
		SPDT_HALF4X3,
		SPDT_HALF4,
		SPDT_HALF2X4,
		SPDT_HALF3X4,
		SPDT_HALF4X4,

		SPDT_INT,
		SPDT_INT2,
		SPDT_INT3,
		SPDT_INT2X3,
		SPDT_INT3X3,
		SPDT_INT4X3,
		SPDT_INT4,
		SPDT_INT2X4,
		SPDT_INT3X4,
		SPDT_INT4X4,

		SPDT_FLOAT,
		SPDT_FLOAT2,
		SPDT_FLOAT3,
		SPDT_FLOAT2X3,
		SPDT_FLOAT3X3,
		SPDT_FLOAT4X3,
		SPDT_FLOAT4,
		SPDT_FLOAT2X4,
		SPDT_FLOAT3X4,
		SPDT_FLOAT4X4,

		SPDT_SAMPLER_2D,
		SPDT_SAMPLER_3D,
		SPDT_SAMPLER_CUBE,

		SPDT_USER
	};

	struct GRAPHICS_ENTRY ShaderParamDesc
	{
		ShaderParamCategory Category;
		ShaderParamDataType DataType;
		int ImplHandle;
		char Name[MAX_SHADER_PARAM_NAME_LEN];
		bool ExposeToApp;
	};

	/** internal class for shader parameter. */
	class GRAPHICS_ENTRY _RawBuffer
	{
	public:
		void* data()
		{
			return mData;
		}

		u16 sizeInBytes() const
		{
			return mSizeInBytes;
		}

		void resize(u16 sizeInBytes);

		_RawBuffer();
		_RawBuffer(u16 sizeInBytes);
		_RawBuffer(const _RawBuffer& rb);
		virtual ~_RawBuffer();

		_RawBuffer& operator = (const _RawBuffer& rb);

	private:
		u8* mData;
		u16 mSizeInBytes;
	};

	struct _SupportShdDataCat {};
	struct _UnsupportShdDataCat {};

	template <typename T>
	class _ShdParamDataType
	{
	public:
		typedef _UnsupportShdDataCat cat_tag;
	};

	//////////////////////////////////////////////////////////////////////////
	// currently support types

	template <>
	class _ShdParamDataType<float>
	{
	public:
		typedef _SupportShdDataCat cat_tag;
	};

	template <>
	class _ShdParamDataType<int>
	{
	public:
		typedef _SupportShdDataCat cat_tag;
	};

	template <>
	class _ShdParamDataType<Vector2>
	{
	public:
		typedef _SupportShdDataCat cat_tag;
	};

	template <>
	class _ShdParamDataType<Vector3>
	{
	public:
		typedef _SupportShdDataCat cat_tag;
	};

	template <>
	class _ShdParamDataType<Vector4>
	{
	public:
		typedef _SupportShdDataCat cat_tag;
	};

	template <>
	class _ShdParamDataType<Matrix3x3>
	{
	public:
		typedef _SupportShdDataCat cat_tag;
	};

	template <>
	class _ShdParamDataType<Matrix4x4>
	{
	public:
		typedef _SupportShdDataCat cat_tag;
	};

	template <>
	class _ShdParamDataType<Pointi>
	{
	public:
		typedef _SupportShdDataCat cat_tag;
	};

	template <>
	class _ShdParamDataType<Vec3i>
	{
	public:
		typedef _SupportShdDataCat cat_tag;
	};

	template <>
	class _ShdParamDataType<Vec4i>
	{
	public:
		typedef _SupportShdDataCat cat_tag;
	};

	// end currently support types
	//////////////////////////////////////////////////////////////////////////

	/** base class of shader parameters.
	*/
	class GRAPHICS_ENTRY ShaderParameter
	{
	public:
		ShaderParameter()
			:mHandle(0)
			,mDataType(SPDT_USER)
		{

		}
		virtual ~ShaderParameter() { mBuffer.resize(0); }

		virtual ShaderParamCategory getCategory() const = 0;

		/** for shader program to create it. */
		void create(const ShaderParamDesc& desc);

		const _string& getName() const
		{
			return mName;
		}

		template <typename T>
		bool setData(const T& t)
		{
			if(sizeof(T) != mBuffer.sizeInBytes())
				return false;

			T& myVal = _raw_Cast<T>(_ShdParamDataType<T>::cat_tag());
			myVal = t;

			return false;
		}

		template <typename T>
		T* getData()
		{
			if(sizeof(T) != mBuffer.sizeInBytes())
				return NULL;

			return _raw_CastPtr<T>(_ShdParamDataType<T>::cat_tag());
		}

		bool isValid() const
		{
			return mBuffer.sizeInBytes() > 0;
		}

		bool isValid(ShaderParamDataType spdt) const
		{
			return mDataType == spdt && mBuffer.sizeInBytes() > 0;
		}

		u16 getSizeInBytes() const
		{
			return mBuffer.sizeInBytes();
		}

		static u32 DataTypeSize(ShaderParamDataType spdt);

		ShaderParamDataType getDataType() const
		{
			return mDataType;
		}

		RRHandle getHandle() const
		{
			return mHandle;
		}

		void setHandle(RRHandle handle)
		{
			mHandle = handle;
		}

	private:
		template <typename T>
		T& _raw_Cast(_SupportShdDataCat)
		{
			return *((T*)mBuffer.data());
		}

		template <typename T>
		T* _raw_CastPtr(_SupportShdDataCat)
		{
			return (T*)mBuffer.data();
		}

	protected:
		_string mName;
		RRHandle mHandle;

		// for safety, can only base class access these members.
	private:
		ShaderParamDataType mDataType;
		_RawBuffer mBuffer;
	};

	/** user-defined shader parameter */
	class GRAPHICS_ENTRY ShaderParamApp : public ShaderParameter
	{
	public:
		ShaderParamCategory getCategory() const
		{
			return SPC_APP;
		}

		ShaderParamApp(const _string& name, ShaderParamDataType spd);

	};

	// class Texture;

	enum SamplerType
	{
		SAMPLER_1D,
		SAMPLER_2D,
		SAMPLER_3D,
		SAMPLER_CUBE,
		SAMPLER_EMPTY,
		SAMPLER_TYPE_NUM
	};

	// FIXME this should be according to platform definition.
	enum { MAX_NUM_SAMPLERS = 8 };

	struct GRAPHICS_ENTRY _SamplerInfo
	{
		ColorDW mBorderColor;
		float mAnisotropic;
		float mLodBias;
		u16 mFilter;
		u8 mWrapU;
		u8 mWrapV;
		u8 mWrapW;
		u8 mSamplerType;
		u32 mTextureID;

		_SamplerInfo();
	};

	// TODO
	class GRAPHICS_ENTRY ShaderSampler : public ShaderParameter
	{
	public:
		enum FilerElem
		{
			SFE_MIN_POINT = 0,
			SFE_MIN_LINEAR = 0x1,
			SFE_MAG_POINT = 0,
			SFE_MAG_LINEAR = 0x2,
			SFE_MIP_POINT = 0,
			SFE_MIP_LINEAR = 0x4,
			SFE_ANISOTROPIC = 0xF,
			SFE_COMPARISON = 0x10
		};

		enum Filter
		{
			SF_MIN_MAG_MIP_POINT = 0,
			SF_MIN_MAG_POINT_MIP_LINEAR = SFE_MIP_LINEAR,
			SF_MIN_POINT_MAG_LINEAR_MIP_POINT = SFE_MAG_LINEAR,
			SF_MIN_POINT_MAG_MIP_LINEAR = SFE_MAG_LINEAR | SFE_MIP_LINEAR,
			SF_MIN_LINEAR_MAG_MIP_POINT = SFE_MIN_LINEAR,
			SF_MIN_LINEAR_MAG_POINT_MIP_LINEAR = SFE_MIN_LINEAR | SFE_MIP_LINEAR,
			SF_MIN_MAG_LINEAR_MIP_POINT = SFE_MIN_LINEAR | SFE_MAG_LINEAR,
			SF_MIN_MAG_MIP_LINEAR = SFE_MIN_LINEAR | SFE_MAG_LINEAR | SFE_MIP_LINEAR,
			SF_ANISOTROPIC = SFE_ANISOTROPIC,

			SF_CMP_MIN_MAG_MIP_POINT = SFE_COMPARISON,
			SF_CMP_MIN_MAG_POINT_MIP_LINEAR = SFE_COMPARISON | SF_MIN_MAG_POINT_MIP_LINEAR,
			SF_CMP_MIN_POINT_MAG_LINEAR_MIP_POINT = SFE_COMPARISON | SF_MIN_POINT_MAG_LINEAR_MIP_POINT,
			SF_CMP_MIN_POINT_MAG_MIP_LINEAR = SFE_COMPARISON | SF_MIN_POINT_MAG_MIP_LINEAR,
			SF_CMP_MIN_LINEAR_MAG_MIP_POINT = SFE_COMPARISON | SF_MIN_LINEAR_MAG_MIP_POINT,
			SF_CMP_MIN_LINEAR_MAG_POINT_MIP_LINEAR = SFE_COMPARISON | SF_MIN_LINEAR_MAG_POINT_MIP_LINEAR,
			SF_CMP_MIN_MAG_LINEAR_MIP_POINT = SFE_COMPARISON | SF_MIN_MAG_LINEAR_MIP_POINT,
			SF_CMP_MIN_MAG_MIP_LINEAR = SFE_COMPARISON | SF_MIN_MAG_MIP_LINEAR,
			SF_CMP_ANISOTROPIC = SFE_COMPARISON | SFE_ANISOTROPIC,
		};

		enum WrapMode
		{
			WM_NONE,							// DX : 0 OGL : GL_CLAMP
			WM_CLAMP,						// DX : D3DTADDRESS_CLAMP OGL : GL_CLAMP
			WM_WRAP,							// DX : D3DTADDRESS_WRAP OGL : OGL_REPEAT
			WM_MIRROR,						// DX : D3DTADDRESS_MIRROR GL_MIRRORED_REPEAT
			WM_BORDER,						// DX : D3DTADDRESS_BORDER GL_CLAMP_TO_BORDER
			WM_CLAMP_EDGE,				// DX : D3DTADDRESS_CLAMP GL_CLAMP_TO_EDGE

			WM_NUM
		};

		ShaderSampler();

		ShaderParamCategory getCategory() const
		{
			return SPC_SAMPLER;
		}

		_SamplerInfo getSamplerInfo()
		{
			return mInfo;
		}

NEZHA_INTERNAL:
		//TexturePtr mTexture;

	private:
		_SamplerInfo mInfo;
	};

}//end namespace Nezha

#endif //end __SHADER_PARAMETER_H__